#pragma once
#include<algorithm>
#include <cstdint>
#include <cstdio>
//#include<iostream>
#include <mutex>
#include <string>
#include <vector>
#include"oj_model2.hpp"
//#include"oj_model.hpp"
#include"oj_view.hpp"
#include<cassert>
#include<fstream>
#include <jsoncpp/json/json.h>
#include"../comm/httplib.h"


#include"../comm/util.hpp"
#include"../comm/log.hpp"

namespace  ns_control
{
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_oj_model;
    using namespace ns_oj_view;
    using namespace httplib;

    class Machine
    {
        public:
            Machine()
            :ip("")
            ,port(0)
            ,load(0)
            ,mtx(nullptr)
            {}

            void IncLoad()//增加负载
            {
                if(mtx)//锁存在
                {
                    mtx->lock();
                }
                ++load;
                 if(mtx)
                {
                    mtx->unlock();
                }
            }
            void DecLoad()//减少负载
            {
                if(mtx)
                {
                    mtx->lock();
                }
                --load;
                 if(mtx)
                {
                   mtx->unlock();
                 }
            }
            //将重新上线的主机负载置0；
            void ResetLoad() 
            {
              if (mtx)
                mtx->lock();
              load = 0;
              if (mtx)
                mtx->unlock();
            }

            uint64_t Load()//只是为了统一接口；
            {
                uint64_t _load=0;
                if(mtx)
                {
                    mtx->lock();
                }
                _load=load;
                if(mtx)
                {
                    mtx->unlock();
                }

                return _load;
            }

        public:
            std::string ip;
            int port;
            uint64_t load;
            std::mutex* mtx;//?
    };

    const std::string service_machine="./conf/service_machine.conf";
    class LoadBalance
    {
    public:
        LoadBalance()
        {
            assert(LoadConf(service_machine));
            LOG(INFO)<<"加载配置文件"<<service_machine<<"成功"<<"\n";
        }
        ~LoadBalance()
        {}

        bool LoadConf(const std::string& machine_conf)
        {
            std::ifstream in(machine_conf);
            if(!in.is_open())
            {
                LOG(FATAL)<<"加载配置文件: "<<machine_conf<<"失败"<<"\n";
                return false;
            }
            std::string line;
            while(getline(in,line))
            {
                //127.0.0.1:8081
                vector<std::string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if(tokens.size()!=2)
                {
                    LOG(WARNING)<<"主机配置文件切分IP，Port失败"<<"\n";//\n仅换行，endl还会刷新缓冲区；
                }
                Machine m;
                m.ip=tokens[0];
                m.port=atoi(tokens[1].c_str());
                m.load=0;
                m.mtx=new std::mutex();//?

                online.push_back(machines.size());//将下标看做主机id进行管理，依次填入0,1,2...
                machines.push_back(m);
            }

            in.close();
            return true; 

        }

        
        //for test
        void ShowMachines()
        {
             mtx.lock();
             std::cout << "当前在线主机列表: ";
             for(auto &id : online)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             std::cout << "当前离线主机列表: ";
             for(auto &id : offline)
             {
                 std::cout << id << " ";
             }
             std::cout << std::endl;
             mtx.unlock();
        }

        // 使用选择负载最小的主机，轮训+哈希；
        //两个都是输出型参数
        bool SmartChoice(int *id,Machine** m)
        {
            mtx.lock();
            int online_num=online.size();
            if(online_num==0)
            {
                mtx.unlock();
                LOG(FATAL)<<"所有主机均已离线，请尽快维护"<<"\n";
                return false;
            }

            *id=online[0];
            *m=&machines[online[0]];
            //bug!导致一台主机离线后面主机都离线的问题-> *m=&machines[0];
            uint64_t min_load=machines[online[0]].Load();
            for(int i=0;i<online_num;i++)
            {
                uint64_t cur_load=machines[online[i]].Load();
                if(min_load>machines[online[i]].load)
                {
                    min_load = cur_load;
                    *id = online[i];
                    *m = &machines[i];
                }
            }
            mtx.unlock();
            return true;
        }

        void OfflineMachine(int which)
        {
            mtx.lock();
            for(auto iter = online.begin(); iter != online.end(); iter++)
            {
                if(*iter == which)
                {
                    machines[which].ResetLoad();
                    //要离线的主机已经找到
                    online.erase(iter);
                    offline.push_back(which);
                    break; //因为break的存在，所有我们暂时不考虑迭代器失效的问题
                }
            }
            mtx.unlock();
        }
        void OnlineMachine()
        {
            //我们统一上线，后面统一解决
            mtx.lock();
            online.insert(online.end(), offline.begin(), offline.end());
            offline.erase(offline.begin(), offline.end());
            mtx.unlock();

            LOG(INFO) << "所有主机已重新上线!" << "\n";
        }

    private:
        std::vector<Machine> machines;
        std::vector<int> online;
        std::vector<int> offline;
        std::mutex mtx;
    };


    class Control
    {
        public:
        Control()
        {}

        ~Control()
        {}

        void RecoveryMachine()
        {
            load_blance_.OnlineMachine();
        }

        //根据获取的题目数据构建网页
        bool AllQuestions(std::string *html)//输出型参数
        {
            std::vector<struct Question> all;
            bool ret=true;
            if(model_.GetAllQuestions(&all))
            {
                //让题库中的题目按编号排序
                sort(all.begin(),all.end(),[](const struct Question &q1,const struct Question &q2)
                {
                    return atoi(q1.number.c_str())<atoi(q2.number.c_str());
                });
                //获取所有题目成功,根据数据构建网页信息
                view_.AllExpendHtml(all,html);
            }
            else
            {
                *html="获取所有题目失败";
                ret= false;
            }
            return ret;

        }

        bool Question(std::string &number, std::string *html)
        {
            bool ret=true;
            struct Question q;
            if(model_.GetOneQuestion(number,&q))
            {
                //获取指定题目成功,根据数据构建网页信息

                view_.OneExpendHtml(q,html);
            }
            else
            {
                *html= "指定题目："+number+"不存在！";
                ret=false;
            }
            return ret;
        }

        void Judge(const std::string &number, const std::string in_json, std::string *out_json)
        {
            //查看输入信息
             //LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";
            
            // 0. 根据题目编号，直接拿到对应的题目细节
            struct Question q;
            model_.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化，得到题目的id，得到用户提交源代码，input
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json, in_value);
            std::string code = in_value["code"].asString();

            // 2. 重新拼接用户代码+测试用例代码，形成新的代码
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value);
            // 3. 选择负载最低的主机(差错处理)
            // 规则: 一直选择，直到主机可用，否则，就是全部挂掉
            while(true)
            {
                int id = 0;
                Machine *m = nullptr;
                if(!load_blance_.SmartChoice(&id, &m))
                {
                    break;
                }
                // 4. 然后发起http请求，得到结果
                Client cli(m->ip, m->port);
                m->IncLoad();
                LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 当前主机的负载是: " << m->Load() << "\n";
                if(auto res = cli.Post("/Compile_Run", compile_string, "application/json;charset=utf-8"))
                {
                    // 5. 将结果赋值给out_json
                    if(res->status == 200)//?
                    {
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                    m->DecLoad();
                }
                else
                {
                    //请求失败
                    LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";
                    load_blance_.OfflineMachine(id);
                    load_blance_.ShowMachines(); //仅仅是为了用来调试
                }
            }

        }

        private:
            Model model_;//提供后台数据；
            View view_;//渲染网页功能；
            LoadBalance load_blance_;//负载均衡功能；

    };

}